﻿using BarManagement.DomainModel.Base.Extensions;
using BarManagement.DomainService.Base;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace BarManagement.Infrastructure.DataAccess.Base
{
    public abstract class RepositoryBase<TEntity> : IRepository<TEntity> where TEntity : class
    {
        private readonly IDataContext _context;
        private readonly IDbSet<TEntity> _dbEntitySet;
        private bool _disposed;

        public RepositoryBase(IDataContext context)
        {
            _context = context;
            _dbEntitySet = _context.Set<TEntity>();
        }

        public virtual Task<List<TEntity>> GetAllAsync()
        {
            return _dbEntitySet.ToListAsync();
        }

        public virtual Task<PaginatedList<TEntity>> GetAllAsync(int pageIndex, int pageSize)
        {
            return GetAllAsync(pageIndex, pageSize, null);
        }

        public virtual async Task<PaginatedList<TEntity>> GetAllAsync(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includeProperties)
        {
            var entities = FilterQuery(predicate, includeProperties);
            var total = await entities.CountAsync();
            entities = entities.Paginate(pageIndex, pageSize);
            var list = await entities.ToListAsync();
            return list.ToPaginatedList(pageIndex, pageSize, total);
        }

        public virtual Task<List<TEntity>> GetAllIncludingAsync(params Expression<Func<TEntity, object>>[] includeProperties)
        {
            var entities = IncludeProperties(includeProperties);
            return entities.ToListAsync();
        }

        public virtual Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return _dbEntitySet.FirstOrDefaultAsync(predicate);
        }

        public virtual Task<TEntity> GetSingleIncludingAsync(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includeProperties)
        {
            var entities = IncludeProperties(includeProperties);
            return entities.FirstOrDefaultAsync(predicate);
        }

        public virtual Task<List<TEntity>> FindByAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return _dbEntitySet.Where(predicate).ToListAsync();
        }

        public virtual void Insert(TEntity entity)
        {
            _context.SetAsAdded(entity);
        }

        public virtual void Update(TEntity entity)
        {
            _context.SetAsModified(entity);
        }

        public virtual void Delete(TEntity entity)
        {
            _context.SetAsDeleted(entity);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public virtual void Dispose(bool disposing)
        {
            if (!_disposed && disposing) _context.Dispose();
            _disposed = true;
        }

        private IQueryable<TEntity> FilterQuery(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, object>>[] includeProperties)
        {
            var entities = IncludeProperties(includeProperties);
            entities = (predicate != null) ? entities.Where(predicate) : entities;
            return entities;
        }

        private IQueryable<TEntity> IncludeProperties(params Expression<Func<TEntity, object>>[] includeProperties)
        {
            IQueryable<TEntity> entities = _dbEntitySet;
            foreach (var includeProperty in includeProperties) entities = entities.Include(includeProperty);
            return entities;
        }
    }
}
